
Para este projeto exploraremos os dados Call_Data.csv disponível na pasta.
Para facilitar a administração da segurança pública, o Departamento de Polícia de Seattle dividiu a cidade em 5 partes, cada uma com uma delegacia. Cada delegacia foi subdividida em setores, e estes foram divididos em beats (hondas). A administração tem um dataset chamado Call_Data, para obter maiores informações acesse este link.
O objetivo do nosso projeto é apoiar os policiais quanto as medidas prescritivas que eles devem tomar ao tentarem resolver uma chamada. Para isto eles têm disponível o histórico de tudo o que já foi resolvido, por ele e por seus colegas, e sua solução de Data Science capaz de prever a variável alvo da nossa prova será Event Clearance Description.
Boa prova e hands on!
PS.:
# Para rodar com o tamanho grande trocar a variável para --> False
small_size = False
import pandas as pd
import numpy as np
pd.options.display.max_rows = 150
from pandas.core.common import SettingWithCopyWarning
import warnings
warnings.simplefilter(action="ignore", category=SettingWithCopyWarning)
import nltk
nltk.download(['punkt', 'wordnet'])
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.preprocessing import FunctionTransformer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import MinMaxScaler
from sklearn.multiclass import OneVsRestClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_validate
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import validation_curve
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import f1_score
import plotly.express as px
import matplotlib.pyplot as plt
call_data = pd.read_csv('Call_Data.csv')
del call_data['Unnamed: 0']
call_data.sample()
1. Importe o data set call_data.csv e considere a variável alvo 'Event Clearance Description'(0,5 pontos)
1.1. Como está o balanceamento das classes?
P.S.: Não é obrigatório aplicar o undersampling and oversampling sobre o dataset
P.S.: Se desejar você pode usar o dataset call_data_small.csv apenas pra agilizar o desenvolvimento no final tudo deve ser realizado com o call_data.csv
call_data['Event Clearance Description'].unique()
fig = px.pie(
call_data['Event Clearance Description'].value_counts().reset_index(),
values= 'Event Clearance Description',
names= 'index',
title= 'Event Clearance Description'
)
fig.show()
2. Realize o EDA que você julgar necessário (análise exploratória dos dados), o objetivo do EDA é mostrar alguns insights sobre os dados (1,0 pontos)
print('No Rows:', call_data.shape[0])
print('No Columns:', call_data.shape[1])
col_dict = {}
for col in call_data.columns:
col_dict[str(col)] = {}
col_dict[str(col)]['No Nulls'] = call_data[col].isnull().sum()
col_dict[str(col)]['No Empty'] = (call_data[col] == '').sum()
col_dict[str(col)]['No UNKNOWN'] = (call_data[col] == 'UNKNOWN').sum()
col_dict[str(col)]['No Uniques'] = len(call_data[col].unique())
col_dict[str(col)]['Uniques'] = call_data[col].sort_values().unique()
col_dict = pd.DataFrame.from_dict(col_dict).T
col_dict
O número de valores únicos na coluna identificadora ('CAD Event Number') é realmente único e igual ao número de linhas.
len(call_data['CAD Event Number'].unique())
call_data.shape[0]
Observando os valores únicos foi visto que a coluna de delegacias tem 6 valores únicos, no entanto só há 5 delegacias na cidade. Vamos ver quais são esses valores:
call_data['Precinct'].unique()
A um valor denominado 'UNKNOWN', ou desconhecido, isso então representa a falta de informação ou valor nulo e deve ser tratado.
Além disso, observa-se que o número de celulas com o valor 'UNKNOWN' é igual a número de valores nulos na coluna Setor. Vamos conferir se todos os valores nulos se 'Setor' são em Delegacias igual a 'UNKNOWN'.
call_data[call_data['Precinct'] == 'UNKNOWN'].Sector.unique()
call_data.groupby(['Precinct', 'Sector', 'Beat'])['CAD Event Number'].count().reset_index()
Pela informação da tabela acima vemos que não há nenhum Setor ou Rota alocado em um Delegacia incorreto.
call_data.groupby(['Precinct'])['CAD Event Number'].count().reset_index(
).sort_values(by= 'CAD Event Number', ascending= False).head(5)
É observado que as Precincts que apresentam maior número de chamados são a Oeste e a Norte.
call_data.groupby(['Precinct', 'Sector'])['CAD Event Number'].count().reset_index(
).sort_values(by= 'CAD Event Number', ascending= False).head(5)
Já na visão por Setor, entre o top 5 há três Setor da Delegacias Oeste e dois da Delegacia Sul
call_data.fillna('Nulo').groupby(['Precinct', 'Sector', 'Beat'])['CAD Event Number'].count().reset_index()
Analisando os valores incorretos vemos que, através da rota, podemos corrigir a delegacia e o setor.
list_beats = call_data.groupby(['Precinct', 'Sector'])['Beat'].unique().reset_index()
for i in range(list_beats.shape[0]):
call_data.loc[call_data.Beat.isin(list_beats.iloc[i,:].Beat), 'Precinct'] = list_beats.iloc[i,:].Precinct
call_data.loc[call_data.Beat.isin(list_beats.iloc[i,:].Beat), 'Sector'] = list_beats.iloc[i,:].Sector
call_data.fillna('Nulo').groupby(['Precinct', 'Sector', 'Beat'])['CAD Event Number'].count().reset_index()
print('No de Delegacias com o UNKNOWN:', (call_data['Precinct'] == 'UNKNOWN').sum())
Reduzimos o número de Delegacias sem informação de 1550 para 1022.
call_data = call_data[call_data['Precinct'] != 'UNKNOWN']
fig = px.pie(
call_data.groupby(['Sector'])['CAD Event Number'].count().reset_index(),
values= 'CAD Event Number',
names= 'Sector',
title= 'Sector'
)
fig.show()
Não concentração em nenhum setor na cidade
fig = px.pie(
call_data.groupby(['Beat'])['CAD Event Number'].count().reset_index(),
values= 'CAD Event Number',
names= 'Beat',
title= 'Beat'
)
fig.show()
Não há nenhuma concentração de chamados em nehuma honda
call_data.groupby(['Call Type'])['CAD Event Number'].count().reset_index()
fig = px.pie(
call_data.groupby(['Call Type'])['CAD Event Number'].count().reset_index(),
values= 'CAD Event Number',
names= 'Call Type',
title= 'Call Type'
)
fig.show()
Observa-se que a diversas formas de criar um chamado mas que as principais formas de acionamento são através de ligações diretas, ligações para terceiros e flagrante.
O OHE será realizado nas 3 categorias de maior destaque e os outros serão agrupadas em uma quarta categoria
top_call_type = call_data.groupby(['Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(3)['Call Type'].unique()
for col in top_call_type:
call_data['Call Type ' + col] = (call_data['Call Type'] == col).astype(int)
call_data['Call Type OUTROS'] = (~call_data['Call Type'].isin(top_call_type)).astype(int)
call_data.columns
call_data[['CAD Event Number', 'Call Type', 'Call Type ONVIEW',
'Call Type TELEPHONE OTHER, NOT 911', 'Call Type 911', 'Call Type OUTROS']].sample(10)
dt_format = '%m/%d/%Y %I:%M:%S %p'
call_data['Original Time Queued'] = pd.to_datetime(call_data['Original Time Queued'], format= dt_format)
dt_format = '%b %d %Y %I:%M:%S:%f%p'
call_data['Arrived Time'] = pd.to_datetime(call_data['Arrived Time'], format= dt_format)
call_data['Original Time Queued Month'] = call_data['Original Time Queued'].dt.month
call_data['Original Time Queued Day'] = call_data['Original Time Queued'].dt.day
call_data['Original Time Queued Hour'] = call_data['Original Time Queued'].dt.hour
call_data['Original Time Queued DayOfWeek'] = call_data['Original Time Queued'].dt.dayofweek
call_data['Original Time Queued WeekOfYear'] = call_data['Original Time Queued'].dt.weekofyear
call_data['Time to Arrive Hours'] = (call_data['Arrived Time'] - call_data['Original Time Queued']).dt.total_seconds()/3600
A variavel de hora da chegada tem valores vazios. Logo foram criadas duas colunas, na original, o tempo de chegada foi considerado zero quando não havia a informação, na coluna com "Fix" foi substituido os valores diferentes por valores igual a média do Priority.
time_arrive_mean = call_data[call_data['Time to Arrive Hours'] > 0].groupby('Priority')['Time to Arrive Hours'].mean()
call_data['Time to Arrive Hours Fix'] = call_data['Time to Arrive Hours']
for id_priority in call_data[call_data['Time to Arrive Hours'] < 0]['Priority'].unique():
call_data.loc[(call_data['Time to Arrive Hours'] < 0) & (call_data['Priority'] == id_priority), 'Time to Arrive Hours Fix'] = time_arrive_mean.loc[id_priority]
call_data.loc[(call_data['Time to Arrive Hours'] < 0), 'Time to Arrive Hours'] = 0
call_data.groupby(['Initial Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(10)['CAD Event Number'].sum()
call_data.groupby(['Initial Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(20)['CAD Event Number'].sum()/call_data.shape[0]
Observa-se que o top 10 motivos iniciais de ligação são responsáveis por mais de 50% dos chamados.
call_data.groupby(['Initial Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(5)['CAD Event Number'].sum()
call_data.groupby(['Initial Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(5)['CAD Event Number'].sum()/call_data.shape[0]
Além disso o top 5 é responsável por cerca de 39% dos chamados. E esse são:
call_data.groupby(['Initial Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(5)
top5_inicial_call_type = call_data.groupby(['Initial Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(5)['Initial Call Type'].unique()
top5_inicial_call_type
call_data[call_data['Initial Call Type'].isin(top5_inicial_call_type)].groupby(['Priority', 'Initial Call Type']
)['CAD Event Number'].count()
'SUSPICIOUS STOP' e 'TRAFFIC STOP' figuram entre as que mais geram chamados mas não são as que obtem maior prioridade de chamado
call_data.shape
3. Realize o tratamento que você julgar mais adequado aos dados. (2,0 pontos)
Mudanças:
def import_data(small= False):
# Importando dados
if small == True:
data = pd.read_csv('Call_Data_Small.csv')
elif small == False:
data = pd.read_csv('Call_Data.csv')
else:
data = pd.read_csv('test_call_data.csv')
del data['Unnamed: 0']
# Corrigindo formato das colunas de tempo
dt_format = '%m/%d/%Y %I:%M:%S %p'
data['Original Time Queued'] = pd.to_datetime(data['Original Time Queued'], format= dt_format)
dt_format = '%b %d %Y %I:%M:%S:%f%p'
data['Arrived Time'] = pd.to_datetime(data['Arrived Time'], format= dt_format)
# Criando as variáveis de tempo
data['Original Time Queued Month'] = data['Original Time Queued'].dt.month
data['Original Time Queued Day'] = data['Original Time Queued'].dt.day
data['Original Time Queued Hour'] = data['Original Time Queued'].dt.hour
data['Original Time Queued DayOfWeek'] = data['Original Time Queued'].dt.dayofweek
data['Original Time Queued WeekOfYear'] = data['Original Time Queued'].dt.weekofyear
data['Time to Arrive Hours'] = (data['Arrived Time'] - data['Original Time Queued']).dt.total_seconds()/3600
return data
import_data(small= True).shape
import_data().shape
import_data().sample()
def feature_engineering(df, returned= True):
# Corrigindo valores das colunas Precinct e Sector
list_beats = df.groupby(['Precinct', 'Sector'])['Beat'].unique().reset_index()
for i in range(list_beats.shape[0]):
df.loc[df.Beat.isin(list_beats.iloc[i,:].Beat), 'Precinct'] = list_beats.iloc[i,:].Precinct
df.loc[df.Beat.isin(list_beats.iloc[i,:].Beat), 'Sector'] = list_beats.iloc[i,:].Sector
# Filtrando dados UNKNOWN
df = df[df['Precinct'] != 'UNKNOWN']
# Categorizando Call Type
top_call_type = df.groupby(['Call Type'])['CAD Event Number'].count().sort_values(ascending= False).reset_index(
).head(3)['Call Type'].unique()
for col in top_call_type:
df['Call Type ' + col] = (df['Call Type'] == col).astype(int)
df['Call Type Others'] = (~df['Call Type'].isin(top_call_type)).astype(int)
del df['Call Type']
# Corrigindo variável de tempo de chegada
time_arrive_mean = df[df['Time to Arrive Hours'] > 0].groupby('Priority')['Time to Arrive Hours'].mean()
df['Time to Arrive Hours Fix'] = df['Time to Arrive Hours']
for id_priority in df[df['Time to Arrive Hours'] < 0]['Priority'].unique():
df.loc[(df['Time to Arrive Hours'] < 0) & (df['Priority'] == id_priority), 'Time to Arrive Hours Fix'] = time_arrive_mean.loc[id_priority]
df.loc[(df['Time to Arrive Hours'] < 0), 'Time to Arrive Hours'] = 0
del df['Original Time Queued'], df['Arrived Time']
# Deletando informação do Futuro
del df['Final Call Type']
# Deletando coluna não utilizada
del df['Beat']
# Label Encode Target
label_encod = LabelEncoder()
df['Event Clearance Description'] = label_encod.fit_transform(df['Event Clearance Description'])
#print(dict(zip(label_encod.classes_, label_encod.transform(label_encod.classes_))))
# Renomeando as colunas
df.columns = [
'event_id', 'target', 'priority', 'initial_call_type', 'precinct', 'sector',
'call_month', 'call_day', 'call_hour', 'call_dayofweek', 'call_weekofyear',
'hours_to_arrive',
'call_type_onview', 'call_type_not_911', 'call_type_911', 'call_type_others', 'hours_to_arrive_fix'
]
# Trocando ordem das colunas
df = df[[
'event_id', 'target', 'priority', 'initial_call_type', 'precinct', 'sector',
'call_month', 'call_day', 'call_hour', 'call_dayofweek', 'call_weekofyear',
'hours_to_arrive', 'hours_to_arrive_fix',
'call_type_onview', 'call_type_not_911', 'call_type_911', 'call_type_others'
]]
#del df['initial_call_type']
if returned:
return df
else:
return df, list_beats, top_call_type, time_arrive_mean, label_encod
feature_engineering(import_data()).shape
feature_engineering(import_data()).sample()
4. Selecione duas soluções candidatas e justifique suas escolhas. Mostre os pontos negativos e positivos de cada modelo. (2,0 pontos)
Os algoritmos escolhidos foram selecionados levando em consideração dois critérios principais: Simplicidade e Explicabilidade.
O algoritmo de Regressão Logística vai utilizar de equações simples e seus indices facilitam a visualização da importancia da variável.
O algoritmo de Árvore de Decisão tem sua explicabilidade simplificada pela sua forma de execução e também tem formas de visualizar a importancia das variáveis
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
5. Construa os modelos de aprendizagem de máquina para cada modelo (1,0 ponto)
def tokenize(text):
tokens = word_tokenize(text)
lemmatizer = WordNetLemmatizer()
clean_tokens = []
for tok in tokens:
if tok.isalpha():
clean_tok = lemmatizer.lemmatize(tok).lower().strip()
clean_tokens.append(clean_tok)
return clean_tokens
#[tokenize(text) for text in call_data['Initial Call Type'].head(10).values]
def split(df):
X = df.drop(columns= ['event_id', 'target'])
y = df['target']
return X, y
A pipeline trata:
def build_pipeline(classifier):
numerical = ['priority', 'call_month', 'call_day', 'call_hour', 'call_dayofweek', 'call_weekofyear',
'hours_to_arrive', 'hours_to_arrive_fix', 'call_type_onview',
'call_type_not_911', 'call_type_911', 'call_type_others']
categorical = ['precinct', 'sector']
nlp = 'initial_call_type'
nlp_pipeline = Pipeline([
('vect', CountVectorizer(tokenizer= tokenize)),
('tfidf', TfidfTransformer())
])
data_pipeline = ColumnTransformer([
('cat', OneHotEncoder(), categorical),
('num', MinMaxScaler(), numerical),
('nlp', nlp_pipeline, nlp)
])
pipeline = Pipeline([
('data', data_pipeline),
('clf', OneVsRestClassifier(classifier))
])
return pipeline
classifier = LogisticRegression(class_weight= 'balanced')
pipeline = build_pipeline(classifier)
X, y = split(feature_engineering(import_data(small= small_size)))
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state= 100, stratify= y)
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(classification_report(y_test.values, y_pred, zero_division= False))
confusion_matrix(y_test, y_pred)
classifier = DecisionTreeClassifier(class_weight= 'balanced')
pipeline = build_pipeline(classifier)
X, y = split(feature_engineering(import_data(small= small_size)))
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state= 100, stratify= y)
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(classification_report(y_test.values, y_pred, zero_division= False))
confusion_matrix(y_test, y_pred)
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve
import numpy as np
def plot_learning_curve(estimator, title, X, y, axes=None, ylim=None, cv=None,
n_jobs=None, train_sizes=np.linspace(.1, 1.0, 5)):
"""
Generate 3 plots: the test and training learning curve, the training
samples vs fit times curve, the fit times vs score curve.
Parameters
----------
estimator : estimator instance
An estimator instance implementing `fit` and `predict` methods which
will be cloned for each validation.
title : str
Title for the chart.
X : array-like of shape (n_samples, n_features)
Training vector, where ``n_samples`` is the number of samples and
``n_features`` is the number of features.
y : array-like of shape (n_samples) or (n_samples, n_features)
Target relative to ``X`` for classification or regression;
None for unsupervised learning.
axes : array-like of shape (3,), default=None
Axes to use for plotting the curves.
ylim : tuple of shape (2,), default=None
Defines minimum and maximum y-values plotted, e.g. (ymin, ymax).
cv : int, cross-validation generator or an iterable, default=None
Determines the cross-validation splitting strategy.
Possible inputs for cv are:
- None, to use the default 5-fold cross-validation,
- integer, to specify the number of folds.
- :term:`CV splitter`,
- An iterable yielding (train, test) splits as arrays of indices.
For integer/None inputs, if ``y`` is binary or multiclass,
:class:`StratifiedKFold` used. If the estimator is not a classifier
or if ``y`` is neither binary nor multiclass, :class:`KFold` is used.
Refer :ref:`User Guide <cross_validation>` for the various
cross-validators that can be used here.
n_jobs : int or None, default=None
Number of jobs to run in parallel.
``None`` means 1 unless in a :obj:`joblib.parallel_backend` context.
``-1`` means using all processors. See :term:`Glossary <n_jobs>`
for more details.
train_sizes : array-like of shape (n_ticks,)
Relative or absolute numbers of training examples that will be used to
generate the learning curve. If the ``dtype`` is float, it is regarded
as a fraction of the maximum size of the training set (that is
determined by the selected validation method), i.e. it has to be within
(0, 1]. Otherwise it is interpreted as absolute sizes of the training
sets. Note that for classification the number of samples usually have
to be big enough to contain at least one sample from each class.
(default: np.linspace(0.1, 1.0, 5))
"""
if axes is None:
_, axes = plt.subplots(1, 3, figsize=(20, 5))
axes[0].set_title(title)
if ylim is not None:
axes[0].set_ylim(*ylim)
axes[0].set_xlabel("Training examples")
axes[0].set_ylabel("Score")
train_sizes, train_scores, test_scores, fit_times, _ = \
learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs,
train_sizes=train_sizes,
return_times=True)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
fit_times_mean = np.mean(fit_times, axis=1)
fit_times_std = np.std(fit_times, axis=1)
# Plot learning curve
axes[0].grid()
axes[0].fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
axes[0].fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1,
color="g")
axes[0].plot(train_sizes, train_scores_mean, 'o-', color="r",
label="Training score")
axes[0].plot(train_sizes, test_scores_mean, 'o-', color="g",
label="Cross-validation score")
axes[0].legend(loc="best")
# Plot n_samples vs fit_times
axes[1].grid()
axes[1].plot(train_sizes, fit_times_mean, 'o-')
axes[1].fill_between(train_sizes, fit_times_mean - fit_times_std,
fit_times_mean + fit_times_std, alpha=0.1)
axes[1].set_xlabel("Training examples")
axes[1].set_ylabel("fit_times")
axes[1].set_title("Scalability of the model")
# Plot fit_time vs score
axes[2].grid()
axes[2].plot(fit_times_mean, test_scores_mean, 'o-')
axes[2].fill_between(fit_times_mean, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1)
axes[2].set_xlabel("fit_times")
axes[2].set_ylabel("Score")
axes[2].set_title("Performance of the model")
return plt
fig, axes = plt.subplots(3, 2, figsize=(10, 15))
X, y = split(feature_engineering(import_data(small= small_size)))
title = "Learning Curves (LogisticRegression)"
cv = StratifiedKFold(n_splits= 5)
estimator = build_pipeline(LogisticRegression(class_weight= 'balanced'))
plot_learning_curve(estimator, title, X, y, axes=axes[:, 0],
cv=cv, n_jobs= -1)
title = r"Learning Curves (DecisionTreeClassifier)"
cv = StratifiedKFold(n_splits= 5)
estimator = build_pipeline(DecisionTreeClassifier(class_weight= 'balanced'))
plot_learning_curve(estimator, title, X, y, axes=axes[:, 1],
cv=cv, n_jobs= -1)
plt.show()
6. Para cada modelo aplique uma combinação aos hiperparâmetros com o GridSearch e aplique também o CrossValidation (2,0 pontos)
Respostas: Na avaliação dos modelos é importante utilizarmos o GridSearch e o CrossValidation para que seja realizado um treino adequado do modelo e para serem testados os diversos valores de hiper-parâmetros.
Para atingir isso, é necessário separar uma parte dos dados para que seja validado o treino realizado com o modelo. Pode ocorrer então que, ao pegar aleatóriamente dados do conjunto, a separação resulte em um resultado melhor do que o encontrado em outro conjunto de dados aleatórios. Desta forma, a fim de evitar essa "sorte" utilizamos a Validação Cruzada (CrossValidation). A validação cruzada divide o conjunto de dados completo em conjuntos e o modelo é treinado para cada um deles tendo como resultado o valor médio das execuções. Desta forma, mesmo que ocorra alguma "sorte" na separação de algum conjunto visualizaremos também o resultados dos outros conjuntos.
Além disso, para aprimorar os modelos podemos usar o GridSearch. O GridSearch será responsável por executar o treino do modelo de forma iterativa testando a grade de hiper-parâmetros que foi passada a ele, desta forma podemos, de forma automatizada, realizar diversas execuções e armazenar seus resultados.
LRparam_grid = {
'clf__estimator__C': [10, 100],
'clf__estimator__penalty': ['l2'],
'clf__estimator__max_iter': [500, 800]
}
cv = StratifiedKFold(n_splits= 3)
scoring_list = ['accuracy', 'f1_weighted']
X, y = split(feature_engineering(import_data(small= small_size)))
pipeline = build_pipeline(LogisticRegression(class_weight= 'balanced'))
grid_cv = GridSearchCV(
estimator= pipeline,
param_grid= LRparam_grid,
scoring= scoring_list,
n_jobs= -1,
refit= 'f1_weighted',
cv= cv
)
grid_cv.fit(X, y)
pd.DataFrame.from_dict(grid_cv.cv_results_).sort_values('rank_test_f1_weighted')
grid_cv.best_params_
DTparam_grid = {
'clf__estimator__max_depth': [10, 100, 500],
'clf__estimator__min_samples_leaf': [100, 500]
}
cv = StratifiedKFold(n_splits= 3)
scoring_list = ['accuracy', 'f1_weighted']
X, y = split(feature_engineering(import_data(small= small_size)))
pipeline = build_pipeline(DecisionTreeClassifier(class_weight= 'balanced'))
grid_cv2 = GridSearchCV(
estimator= pipeline,
param_grid= DTparam_grid,
scoring= scoring_list,
n_jobs= -1,
refit= 'f1_weighted',
cv= cv
)
grid_cv2.fit(X, y)
pd.DataFrame.from_dict(grid_cv2.cv_results_).sort_values('rank_test_f1_weighted')
7. Defina uma métrica de avaliação e avalie as soluções candidatas. Justifique a escolha da sua métrica. (1,0 ponto)
Nossa metrica escolhida entra as candidatas foi a F1-Score. Com a F1-Score temos uma visão mais abrangente do modelo pois com ela temos como ter uma ideia geral do desempenho do modelo tanto com a precisão quanto ao recall evitando assim uma analise apenas em uma métrica direta.
A F1-Score derida da fórmula: F1 = 2 (precision recall) / (precision + recall).
def cross_validade_model(classifier):
pipeline = build_pipeline(classifier)
X, y = split(feature_engineering(import_data(small= small_size)))
skf = StratifiedKFold(n_splits= 5)
scoring_list = ['accuracy', 'f1_weighted']
scores = cross_validate(pipeline, X, y, cv= skf, scoring= scoring_list, n_jobs= -1)
return scores
fit = {}
classifier_list = [LogisticRegression(class_weight= 'balanced'), DecisionTreeClassifier(class_weight= 'balanced')]#, RandomForestClassifier(), KNeighborsClassifier()]
for classifier in classifier_list:
scores = cross_validade_model(classifier)
fit[str(classifier)] = pd.DataFrame.from_dict(scores).mean().to_dict()
model_fit_result = pd.DataFrame.from_dict(fit)
model_fit_result
Como observado na Tabela, o algoritmo de Regressão Logistica apresentou melhor resultado na métrica F1-Score, logo esse será o modelo escolhido.
8. Escolha um dos modelos, por exemplo o melhor modelo, e faça uma análise do overfitting e underfitting. Justique sua resposta com base em experimentos. (1,5 pontos)
param_range = [0.0001, 0.01, 0.5, 1]
pipeline = build_pipeline(LogisticRegression(class_weight= 'balanced'))
X, y = split(feature_engineering(import_data(small= small_size)))
train_scores, test_scores = validation_curve(
estimator= pipeline,
X= X,
y= y,
param_name= 'clf__estimator__C',
param_range= param_range,
scoring= "f1_weighted",
n_jobs= -1)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.title("Validation Curve with LogisticRegression")
plt.xlabel('C')
plt.ylabel("Score")
plt.ylim(0.6, 0.71)
lw = 2
plt.semilogx(param_range, train_scores_mean, label="Training score",
color="darkorange", lw=lw)
plt.fill_between(param_range, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.2,
color="darkorange", lw=lw)
plt.semilogx(param_range, test_scores_mean, label="Cross-validation score",
color="navy", lw=lw)
plt.fill_between(param_range, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.2,
color="navy", lw=lw)
plt.legend(loc="best")
plt.show()
param_range = [5, 10, 20, 50]
pipeline = build_pipeline(LogisticRegression(class_weight= 'balanced'))
X, y = split(feature_engineering(import_data(small= small_size)))
train_scores2, test_scores2 = validation_curve(
estimator= pipeline,
X= X,
y= y,
param_name= 'clf__estimator__max_iter',
param_range= param_range,
scoring= "f1_weighted",
n_jobs= -1)
train_scores_mean = np.mean(train_scores2, axis=1)
train_scores_std = np.std(train_scores2, axis=1)
test_scores_mean = np.mean(test_scores2, axis=1)
test_scores_std = np.std(test_scores2, axis=1)
plt.title("Validation Curve with LogisticRegression")
plt.xlabel('Max_Iter')
plt.ylabel("Score")
plt.ylim(0.64, 0.725)
lw = 2
plt.semilogx(param_range, train_scores_mean, label="Training score",
color="darkorange", lw=lw)
plt.fill_between(param_range, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.2,
color="darkorange", lw=lw)
plt.semilogx(param_range, test_scores_mean, label="Cross-validation score",
color="navy", lw=lw)
plt.fill_between(param_range, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.2,
color="navy", lw=lw)
plt.legend(loc="best")
plt.show()
9. Realize a predição sobre os dados test_call_data.csv, disponibilizado a parte do arquivo Call_Data.csv, como o seu modelo saiu? (1,0 ponto)
X, y = split(feature_engineering(import_data(small= small_size)))
pipeline = build_pipeline(
LogisticRegression(
C= 100,
max_iter= 500,
penalty= 'l2',
class_weight= 'balanced'
))
pipeline.fit(X, y)
(Aplicando o mesmo que foi feito no de Treino/Teste)
def feature_engineering_test(df, list_beats, top_call_type, time_arrive_mean, label_encod):
# Corrigindo valores das colunas Precinct e Sector
for i in range(list_beats.shape[0]):
df.loc[df.Beat.isin(list_beats.iloc[i,:].Beat), 'Precinct'] = list_beats.iloc[i,:].Precinct
df.loc[df.Beat.isin(list_beats.iloc[i,:].Beat), 'Sector'] = list_beats.iloc[i,:].Sector
# Filtrando dados UNKNOWN
df = df[df['Precinct'] != 'UNKNOWN']
# Categorizando Call Type
for col in top_call_type:
df['Call Type ' + col] = (df['Call Type'] == col).astype(int)
df['Call Type Others'] = (~df['Call Type'].isin(top_call_type)).astype(int)
del df['Call Type']
# Corrigindo variável de tempo de chegada
df['Time to Arrive Hours Fix'] = df['Time to Arrive Hours']
for id_priority in df[df['Time to Arrive Hours'] < 0]['Priority'].unique():
df.loc[(df['Time to Arrive Hours'] < 0) & (df['Priority'] == id_priority), 'Time to Arrive Hours Fix'] = time_arrive_mean.loc[id_priority]
df.loc[(df['Time to Arrive Hours'] < 0), 'Time to Arrive Hours'] = 0
del df['Original Time Queued'], df['Arrived Time']
# Deletando informação do Futuro
del df['Final Call Type']
# Deletando coluna não utilizada
del df['Beat']
# Label Encode Target
df['Event Clearance Description'] = label_encod.fit_transform(df['Event Clearance Description'])
#print(dict(zip(label_encod.classes_, label_encod.transform(label_encod.classes_))))
# Renomeando as colunas
df.columns = [
'event_id', 'target', 'priority', 'initial_call_type', 'precinct', 'sector',
'call_month', 'call_day', 'call_hour', 'call_dayofweek', 'call_weekofyear',
'hours_to_arrive',
'call_type_onview', 'call_type_not_911', 'call_type_911', 'call_type_others', 'hours_to_arrive_fix'
]
# Trocando ordem das colunas
df = df[[
'event_id', 'target', 'priority', 'initial_call_type', 'precinct', 'sector',
'call_month', 'call_day', 'call_hour', 'call_dayofweek', 'call_weekofyear',
'hours_to_arrive', 'hours_to_arrive_fix',
'call_type_onview', 'call_type_not_911', 'call_type_911', 'call_type_others'
]]
#del df['initial_call_type']
return df
(_, list_beats, top_call_type, time_arrive_mean, label_encod
) = feature_engineering(import_data(small= small_size), returned= False)
# Predicted Results
test_call_pred = pipeline.predict(
feature_engineering_test(
df= import_data(small= 'teste'),
list_beats= list_beats,
top_call_type= top_call_type,
time_arrive_mean= time_arrive_mean,
label_encod= label_encod
).drop(columns= ['event_id', 'target']))
# True Results
y_true = feature_engineering_test(
df= import_data(small= 'teste'),
list_beats= list_beats,
top_call_type= top_call_type,
time_arrive_mean= time_arrive_mean,
label_encod= label_encod
)['target'].to_list()
f1_score(y_true, test_call_pred, average='weighted')
Embora levemente abaixo, o F1-Score se manteve bem próximo ao observado no Treino/Teste (0.70)
10. Se seu modelo permitir analisar a importância das features, analise-o e tente justificar de forma subjetiva a importância das features. Por exemplo, a feature chamadas_a_noite possui um alto coeficiente, pois há uma tendência dos crimes acontecerem a noite, não tão simples assim :P. (1,0 ponto)
11. Aplique clusterização, preferencialmente o KMeans sobre o dado, e comunique suas novas descobertas, sinta-se a vontade para apresentar uma solução com recursos visuais (2,0 pontos)
# Exportando arquivo
jupyter nbconvert --to html Prova.ipynb